namespace ScrumSprintMonitor.UI.Wpf.PostSharp
{
	#region #using Directives

	using System;
	using System.ComponentModel;
	using System.Linq;
	using System.Reflection;

	using global::PostSharp.Laos;

	#endregion

	/// <summary>
	/// Implementation of <see cref="OnMethodBoundaryAspect"/> that raises the 
	/// <see cref="INotifyPropertyChanged.PropertyChanged"/> event when a property set
	/// accessor completes successfully.
	/// </summary>
	[Serializable]
	[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
	public sealed class NotifyAttribute : OnMethodBoundaryAspect
	{
		public NotifyAttribute()
		{
			AspectPriority = 10; // Make it appear after GuardDisposedAttribute
		}

		public override void OnEntry(MethodExecutionEventArgs eventArgs)
		{
			if (eventArgs == null)
			{
				return;
			}

			if ((eventArgs.Method.MemberType & MemberTypes.Method) == MemberTypes.Method && eventArgs.Method.Name.StartsWith("set_"))
			{
				OnBindablePropertyEntry(eventArgs, eventArgs.Method.Name.Substring(4));
			}

			base.OnEntry(eventArgs);
		}

		private static void OnBindablePropertyEntry(MethodExecutionEventArgs eventArgs, string propertyName)
		{
			var targetType = eventArgs.Instance.GetType();
			var propertyInfo = targetType.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
			if (propertyInfo == null)
			{
				throw new AccessViolationException();
			}
			if (propertyInfo.GetGetMethod(true) != null)
			{
				var oldValue = propertyInfo.GetValue(eventArgs.Instance, null);
				var newValue = eventArgs.GetReadOnlyArgumentArray().First();
				if (newValue.Equals(oldValue))
				{
					eventArgs.FlowBehavior = FlowBehavior.Return;
				}
			}
		}

		/// <summary>
		/// Executed when the set accessor successfully completes. Raises the 
		/// <see cref="INotifyPropertyChanged.PropertyChanged"/> event.
		/// </summary>
		/// <param name="eventArgs">Event arguments with information about the 
		/// current execution context.</param>
		public override void OnSuccess(MethodExecutionEventArgs eventArgs)
		{
			if (eventArgs == null)
			{
				return;
			}

			if ((eventArgs.Method.MemberType & MemberTypes.Method) == MemberTypes.Method && eventArgs.Method.Name.StartsWith("set_"))
			{
				OnBindablePropertySuccess(eventArgs, eventArgs.Method.Name.Substring(4));
			}
		}

		private static void OnBindablePropertySuccess(MethodExecutionEventArgs eventArgs, string propertyName)
		{
			var firePropertyChanged = eventArgs.Instance as IFirePropertyChanged;
			if (firePropertyChanged != null)
			{
				// Raises the PropertyChanged event.
				firePropertyChanged.OnPropertyChanged(propertyName);
				return;
			}

			// Get the implementation of INotifyPropertyChanged. We have access to it through the IComposed interface,
			// which is implemented at compile time.
			firePropertyChanged =
				(IFirePropertyChanged) ((IComposed<INotifyPropertyChanged>) eventArgs.Instance).GetImplementation(eventArgs.InstanceCredentials);

			// Raises the PropertyChanged event.
			firePropertyChanged.OnPropertyChanged(propertyName);
		}
	}
}